#include "assert.h"
#include "error.h"
#include "string.h"
#include "proc.h"
#include "lock.h"
#include "dbg.h"

#include "fs/deffs.h"
#include "fs/file.h"
#include "fs/inode.h"
#include "fs/vfs.h"
#include "fs/stat.h"
#include "fs/iobuf.h"
#include "fs/dirent.h"

struct {
    // semaphore_t ft_lk;
    spinlock_t ft_lk;
    file_t file[MAX_FILE_NUM];
} f_table;

static inline void
file_lock(file_t* file) {
    spinlock_acquire(&file->file_lk);
}

static inline void
file_unlock(file_t* file) {
    spinlock_release(&file->file_lk);
}

static inline void
file_lock_init(file_t* file) {
    spinlock_init(&file->file_lk, "file");
}

// static file_t*
// get_fd_array(void) {
//     filesStruct_t* fp = getCurrentProc()->fp;
//     assert(fp != NULL && filesCount(fp) > 0);
//     return fp->fd_array;
// }

// void
// fd_array_init(file_t* fd_array) {
//     for (int i = 0; i < FILES_STRUCT_NENTRY; ++i) {
//         fd_array[i].openCount = 0;
//         fd_array[i].status = FD_NONE;
//         fd_array[i].fd = i;
//         file_lock_init(&fd_array[i]);
//     }
// }

void
file_init() {
    __DEBUG_FUNC_START;
    spinlock_init(&f_table.ft_lk, "f_table_lk");
    for (int i = 0; i < MAX_FILE_NUM; ++i)
        memset(&f_table.file[i], 0, sizeof(file_t));
    __DEBUG_FUNC_END;
}

int
__fd_alloc(file_t* f) {
    int fd;
    for (fd = 0; fd < N_OPEN_FILE; ++fd)
        if (getCurrentProc()->openFiles[fd] == NULL) {
            getCurrentProc()->openFiles[fd] = f;
            return fd;
        }
    return -1;  // TODO
}

file_t*
__file_alloc() {
    spinlock_acquire(&f_table.ft_lk);
    for (int i = 0; i < MAX_FILE_NUM; ++i)
        if (f_table.file[i].openCount == 0) {
            f_table.file[i].openCount = 1;
            spinlock_release(&f_table.ft_lk);
            return &f_table.file[i];
        }
    spinlock_release(&f_table.ft_lk);
    return NULL;
}

/**
 * @brief
 *
 * @param fd
 * @param file_store
 * @return int < 0 - error; >= 0 - fd
 */
// static int
// fd_array_alloc(int fd, file_t** file_store) {
//     file_t* fd_array = get_fd_array();
//     if (fd == NO_FD) {
//         for (fd = 0; fd < FILES_STRUCT_NENTRY; ++fd)
//             if (fd_array[fd].status == FD_NONE)
//                 goto found;
//         return -E_MAX_OPEN;
//     } else {
//         if (fd >= 0 && fd < FILES_STRUCT_NENTRY) {
//             if (fd_array[fd].status == FD_NONE)
//                 goto found;
//             return -E_BUSY;
//         }
//         return -E_INVAL;
//     }

// found:
//     assert(fopenCount(&fd_array[fd]) == 0);
//     fd_array[fd].status = FD_INIT;
//     fd_array[fd].node = NULL;
//     *file_store = &fd_array[fd];
//     return fd;
// }

// static void
// fd_array_free(file_t* file) {
//     assert(file->status == FD_INIT || file->status == FD_CLOSED);
//     assert(fopenCount(file) == 0);
//     if (file->status == FD_CLOSED)
//         vfs_close(file->node);
//     file->status == FD_NONE;
// }

// static void
// fd_array_acquire(file_t* file) {
//     assert(file->status == FD_OPENED);
//     file_lock(file);  // TODO
//     fopenCountInc(file);
// }

// static void
// fd_array_release(file_t* file) {
//     assert(file->status == FD_OPENED || file->status == FD_CLOSED);
//     assert(fopenCount(file) > 0);
//     if (fopenCountDec(file) == 0)
//         fd_array_free(file);
//     file_unlock(file);  // TODO
// }

// void
// fd_array_open(file_t* file) {
//     assert(file->status == FD_INIT && file->node != NULL);
//     file->status == FD_OPENED;
//     fopenCountInc(file);
// }

// void
// fd_array_close(file_t* file) {
//     assert(file->status == FD_OPENED);
//     assert(fopenCount(file) > 0);
//     file->status == FD_CLOSED;
//     if (fopenCountDec(file) == 0)
//         fd_array_free(file);
// }

// void
// fd_array_dup(file_t* to, file_t* from) {
//     assert(to->status == FD_INIT && from->status == FD_OPENED);
//     to->pos = from->pos;
//     to->readable = from->readable;
//     to->writeable = from->writeable;
//     inode_t* node = from->node;
//     vop_refInc(node);
//     vop_openInc(node);
//     to->node = node;
//     fd_array_open(to);
// }

int
fd2file(int fd, file_t** file_store) {
    if (fd >= 0 && fd < FILES_STRUCT_NENTRY) {
        file_t* file = getCurrentProc()->openFiles[fd];
        if (file->status == FD_OPENED && file->fd == fd) {
            *file_store = file;
            return 0;
        }
    }
    return -E_INVAL;
}

// bool
// file_testfd(int fd, bool readable, bool writable) {
//     file_t* file;
//     int ret = fd2file(fd, &file);
//     if (ret != 0)
//         return FALSE;
//     if ((readable && !file->readable) || (writable && !file->writeable))
//         return FALSE;
//     return TRUE;
// }

// int
// file_open(char* path, uint32_t flags) {
//     bool readable = FALSE, writeable = FALSE;
//     switch (flags & O_ACCMODE) {
//         case O_RDONLY: {
//             readable = TRUE;
//             break;
//         }
//         case O_WRONLY: {
//             writeable = TRUE;
//             break;
//         }
//         case O_RDWR: {
//             readable = writeable = TRUE;
//             break;
//         }
//         default:
//             return -E_INVAL;
//     }

//     file_t* file;
//     int ret = fd_array_alloc(NO_FD, &file);
//     if (ret < 0)  // error
//         return ret;

//     inode_t* node;
//     ret = vfs_open(path, flags, &node);
//     if (ret < 0) {
//         fd_array_free(file);
//         return ret;
//     }

//     file->pos = 0;
//     if (flags & O_APPEND) {  // move `pos`
//         stat_t __stat;
//         ret = vop_fstat(node, &__stat);
//         if (ret < 0) {
//             vfs_close(node);
//             fd_array_free(file);
//             return ret;
//         }
//         file->pos = __stat.st_size;
//     }
//     file->node = node;
//     file->readable = readable;
//     file->writeable = writeable;
//     fd_array_open(file);
//     return file->fd;
// }

// int
// file_close(int fd) {
//     file_t* file;
//     int ret = fd2file(fd, &file);
//     if (ret < 0)
//         return ret;
//     fd_array_close(file);
//     return 0;
// }

int
__file_close(file_t* f) {
    if (f == NULL)
        return -E_INVAL;
    spinlock_acquire(&f_table.ft_lk);
    if (f->openCount < 1)
        panic("__file_close: on process open this file");
    --f->openCount;
    spinlock_release(&f_table.ft_lk);
    return 0;  // TODO
}

// /**
//  * @brief
//  *
//  * @param fd
//  * @param base 读出内容的起始地址
//  * @param len 期望读取长度
//  * @param copied_store 读取的长度
//  * @return < 0 - error, >=0 read success
//  */
// int
// file_read(int fd, void* base, size_t len, size_t* copied_store) {
//     file_t* file;
//     int ret = fd2file(fd, &file);
//     if (ret < 0)
//         return ret;
//     if (!file->readable)
//         return -E_INVAL;

//     fd_array_acquire(file);
//     iobuf_t __iob;
//     iobuf_t* iob = iobuf_init(&__iob, base, len, file->pos);  // TODO iob中的东西如何处理？
//     ret = vop_read(file->node, iob);                          // TODO iob是如何读文件的

//     size_t copied = iobuf_used(iob);
//     if (file->status = FD_OPENED)
//         file->pos += copied;
//     if (copied_store != NULL)
//         *copied_store = copied;
//     fd_array_release(file);
//     return ret;
// }

// /**
//  * @brief
//  *
//  * @param fd
//  * @param base 待写入内容的起始地址
//  * @param len 期望写入长度
//  * @param copied_store 实际的长度
//  * @return < 0 - error, >=0 read success
//  */
// int
// file_write(int fd, void* base, size_t len, size_t* copied_store) {
//     file_t* file;
//     int ret = fd2file(fd, &file);
//     if (ret < 0)
//         return ret;
//     if (!file->writeable)
//         return -E_INVAL;

//     fd_array_acquire(file);
//     iobuf_t __iob;
//     iobuf_t* iob = iobuf_init(&__iob, base, len, file->pos);
//     ret = vop_write(file->node, iob);

//     size_t copied = iobuf_used(iob);
//     if (file->status == FD_OPENED)
//         file->pos += copied;
//     if (copied_store != NULL)
//         *copied_store = copied;
//     fd_array_release(file);
//     return ret;
// }

// int
// file_seek(int fd, offset_t pos, int startPos) {
//     stat_t __stat;
//     file_t* file;
//     int ret = fd2file(fd, &file);
//     if (ret < 0)
//         return ret;

//     fd_array_acquire(file);  // TODO
//     switch (startPos) {
//         case LSEEK_SET:
//             break;
//         case LSEEK_CUR: {
//             pos += file->pos;
//             break;
//         }
//         case LSEEK_END: {
//             ret = vop_fstat(file->node, &__stat);
//             if (ret >= 0)
//                 pos += __stat.st_size;
//             break;
//         }
//         default: {
//             ret = -E_INVAL;
//             break;
//         }
//     }

//     if (ret >= 0) {
//         ret = vop_trySeek(file->node, pos);
//         if (ret >= 0)
//             file->pos = pos;
//     }
//     fd_array_release(file);  // TODO
//     return ret;
// }

// int
// file_fstat(int fd, stat_t* stat) {
//     file_t* file;
//     int ret = fd2file(fd, &file);
//     if (ret < 0)
//         return ret;

//     fd_array_acquire(file);
//     ret = vop_fstat(file->node, stat);
//     fd_array_release(file);
//     return ret;
// }

// int
// file_fsync(int fd) {
//     file_t* file;
//     int ret = fd2file(fd, &file);
//     if (ret < 0)
//         return ret;

//     fd_array_acquire(file);
//     ret = vop_fsync(file->node);
//     fd_array_release(file);
//     return ret;
// }

// int
// file_getDirEntry(int fd, dirent_t* direntp) {
//     file_t* file;
//     int ret = fd2file(fd, &file);
//     if (ret < 0)
//         return ret;

//     fd_array_acquire(file);
//     iobuf_t __iob;
//     iobuf_init(&__iob, direntp->name, sizeof(direntp->name), direntp->d_off);
//     ret = vop_getDirEntry(file->node, &__iob);
//     if (ret >= 0)
//         direntp->d_off += iobuf_used(&__iob);
//     fd_array_release(file);
//     return ret;
// }

// /**
//  * @brief make a copy of file1 as file2
//  *
//  * @param fd1 source
//  * @param fd2 destination
//  * @return int
//  */
// int
// file_dup(int fd1, int fd2) {
//     int ret;
//     file_t *file1, *file2;
//     ret = fd2file(fd1, &file1);
//     if (ret < 0)
//         return ret;
//     ret = fd2file(fd2, &file2);
//     if (ret < 0)
//         return ret;

//     fd_array_dup(file2, file1);
//     return file2->fd;
// }